package com.rest.common.utils

import org.springframework.data.jpa.domain.Specification
import java.util.*
import javax.persistence.criteria.CriteriaBuilder
import javax.persistence.criteria.Predicate
import javax.persistence.criteria.Root

object SpecificationUtils {
     fun <T> getWhereClause(conditionMap: Map<String, Any?>,searchMap: Map<String, Any?>): Specification<T> {
        return Specification { root, _, cb ->
            val predicate = cb.conjunction()
            this.addCommonExpressions(predicate,root,cb,conditionMap,searchMap)
            predicate
        }
    }

    fun <T> getWhereClause(conditionMap: Map<String, Any?>,searchMap: Map<String, Any?>,inMap:Map<String,Any?>): Specification<T> {
        return Specification { root, _, cb ->
            val predicate = cb.conjunction()
            this.addCommonExpressions(predicate,root,cb,conditionMap,searchMap)
            this.addInExpressions(predicate,root,cb,inMap)
            predicate
        }
    }

    fun <T> getWhereClause(conditionMap: Map<String, Any?>,searchMap: Map<String, Any?>?,beginTimeMap: Map<String, Date?>?,endTimeMap: Map<String, Date?>?): Specification<T> {
        return Specification { root, _, cb ->
            val predicate = cb.conjunction()
            this.addCommonExpressions(predicate,root,cb,conditionMap,searchMap)
            this.addTimeExpressions(predicate,root,cb,beginTimeMap,endTimeMap)
            predicate
        }
    }


    fun <T> getWhereClause(conditionMap: Map<String, Any?>,searchMap: Map<String, Any?>?,beginTimeMap: Map<String, Date?>?,endTimeMap: Map<String, Date?>?,inMap:Map<String,Any?>): Specification<T> {
        return Specification { root, _, cb ->
            val predicate = cb.conjunction()
            this.addCommonExpressions(predicate,root,cb,conditionMap,searchMap)
            this.addTimeExpressions(predicate,root,cb,beginTimeMap,endTimeMap)
            this.addInExpressions(predicate,root,cb,inMap)
            predicate
        }
    }

     fun <T> addCommonExpressions(predicate: Predicate, root: Root<T>, cb: CriteriaBuilder, conditionMap: Map<String, Any?>, searchMap: Map<String, Any?>?) {
        for (fieldName in conditionMap.keys) {
            val value = conditionMap[fieldName]
            predicate.expressions.add(
                    cb.equal(root.get<String>(fieldName), value))
        }
        searchMap?.let {
            var searchList: MutableList<Predicate> = ArrayList<Predicate>()
            for (fieldName in it.keys) {
                val value = it[fieldName]
                searchList.add(cb.like(root.get<String>(fieldName), "%$value%"))
            }
            if (searchList.isNotEmpty()) {
                predicate.expressions.add(cb.or(*searchList.toTypedArray()))
            }
        }
    }

     fun <T> addTimeExpressions(predicate: Predicate, root: Root<T>, cb: CriteriaBuilder,beginTimeMap: Map<String, Date?>?,endTimeMap: Map<String, Date?>?) {
        beginTimeMap?.let {
            for (fieldName in beginTimeMap.keys) {
                val value = beginTimeMap[fieldName]
                predicate.expressions.add(cb.greaterThanOrEqualTo(root.get(fieldName), value))
            }
        }
        endTimeMap?.let {
            for (fieldName in endTimeMap.keys) {
                val value = endTimeMap[fieldName]
                predicate.expressions.add(cb.lessThanOrEqualTo(root.get(fieldName), value))
            }
        }
    }

     fun <T> addInExpressions(predicate: Predicate, root: Root<T>, cb: CriteriaBuilder,inMap:Map<String,Any?>) {
        if (!inMap.isEmpty()) {
            var ins: CriteriaBuilder.In<Any>? = null
            for (fieldName in inMap.keys) {
                val values = inMap[fieldName]
                if( values is Collection<*> && values.isNotEmpty()){
                    ins = cb.`in`(root.get(fieldName))
                    values.forEach { ins?.value(it) }
                }
            }
            predicate.expressions.add(ins)
        }
    }

    fun injectPredicate(cb: CriteriaBuilder, predicate: Predicate, isOr: Boolean) {
        val root = cb.conjunction()
        if (isOr) {
            root.expressions.add(cb.or(predicate))
        } else {
            root.expressions.add(cb.and(predicate))
        }
    }

}
